package HDAPI;

/*
** Copyright (c) 2009
 * 
 * Richard Uren <richard@teleport.com.au>
 * Faisal Thaheem <faisal@computedsynergy.com>
 * 
** All Rights Reserved
**
** --
**
** LICENSE: Redistribution and use in source and binary forms, with or
** without modification, are permitted provided that the following
** conditions are met: Redistributions of source code must retain the
** above copyright notice, this list of conditions and the following
** disclaimer. Redistributions in binary form must reproduce the above
** copyright notice, this list of conditions and the following disclaimer
** in the documentation and/or other materials provided with the
** distribution.
**
** THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
** WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
** MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
** NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
** BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
** OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
** ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
** TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
** USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
** DAMAGE.
**
** --
**
** This is a reference implementation for interfacing with www.handsetdetection.com
**
*/

import java.awt.Rectangle;
import java.net.*;
import java.io.*;
import java.util.HashMap;
import java.util.Enumeration;
import java.text.SimpleDateFormat;
import java.util.Date;
import org.json.*;

public class HDBase {
    
    //Where we will post our queries
    private String m_url = "http://api-us1.handsetdetection.com";
    
    //Our API Key
    private String m_apiKey = "8a4a663ba61f9ac9407894a370871bf3";
    
    //Secret
    private String m_secret = "";
    
    //Username
    private String m_userName = "";
    
    //Timeout in seconds
    private int m_timeOut = 5;
    
    //Parameters to send for detection request
    private JSONObject m_detectRequest = new JSONObject();
    
    //Response of detection
    private JSONObject m_detectReply = new JSONObject();
    
    //Vendor reply
    private JSONObject m_vendorReply = new JSONObject();
    
    //Model reply
    private JSONObject m_modelReply = new JSONObject();
    
    //Error string
    private String m_error = "";
    
    public HDBase(String username, String secret){
        this.m_userName = username;
        this.m_secret = secret;
    }
    
    //setters
    public void setError(String msg) { this.m_error = msg; }
    public void setURL(String url) { this.m_url = url; }
    public void setv2ApiKey(String apiKey) { this.m_apiKey = apiKey; }
    public void setSecret(String secret) { this.m_secret = secret; }
    public void setUsername(String userName) { this.m_userName = userName; }
    public void setTimeout(int timeOut) { this.m_timeOut = timeOut; }
    public void setDetectVar(String key, String val) { try{ this.m_detectRequest.put(key, val); }catch(Exception ex){System.out.println(ex.getMessage());} }
    
    
    //getters
    public String getError() { return this.m_error; }
    public String getURL() { return this.m_url; }
    public String getv2ApiKey() { return this.m_apiKey; }
    public String getSecret() { return this.m_secret; }
    public String getUsername() { return this.m_userName; }
    public int    getTimeout() { return this.m_timeOut; }
    public JSONObject getDetectRequest() { return this.m_detectRequest; }
    public JSONObject getDetect() { return this.m_detectReply; }
    public JSONObject getVendor() { return this.m_vendorReply; }
    public JSONObject getModel() { return this.m_modelReply; }
    
//    public String getDeviceClickToCall() {
//        
//        
//        
//    }
    
    /**
     * Connects to handsetdetection.com - sends the stringified data and 
     * converts the response into a JsonData object
     * 
     * @param data parameters
     * @param service service to query
     * @param optional_headers 
     * @return JSONObject
     */
    private JSONObject post(String data, String service, HashMap optional_headers){

        JSONObject ret = null;
        
        try{
            
            URL url = new URL(this.m_url + service);
            
            URLConnection conn = url.openConnection();
            conn.setDoOutput(true);
            conn.setRequestProperty("Content-Type", "application/json");
            conn.setRequestProperty("ApiKey",this.m_apiKey);
            //conn.setRequestProperty("Authorization",(String)optional_headers.get("Authorization"));
            conn.setRequestProperty("X-wsse",(String)optional_headers.get("X-WSSE"));
            conn.setReadTimeout(this.m_timeOut*1000);
            
            OutputStreamWriter writer = new OutputStreamWriter(conn.getOutputStream());
            writer.write(data);
            writer.flush();
            
            BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String reply;
            String buffer = "";
            while( (reply=reader.readLine()) != null){
                buffer += reply;
            }
            
            writer.close();
            reader.close();
            
            ret = new JSONObject(buffer);
            
        }catch(Exception ex){
            ret = null;
            System.out.println(ex.getMessage());
        }
        
        return ret;
    }

    /**
     * Pre processes the request before sending it to handsetdetection.com
     * @param data
     * @param service Service strings vary depending on the information needed
     * @return JSONObject
     */
    private JSONObject sendjson(JSONObject data, String service){
        
        JSONObject reply = null;
        
        try{
            String wsse = this.generateWSSEHeader(this.m_userName, this.m_secret);
            HashMap optional_headers = new HashMap();
            optional_headers.put("X-WSSE", wsse);
            if(null != data){
                reply = this.post(data.toString(), service, optional_headers);
            }else{
                reply = this.post("", service, optional_headers);
            }
            
        }catch(Exception ex){
            reply = null;
            System.out.println(ex.getMessage());
        }
        
        return reply;
    }
    
    /**
     * Calling this function prior to calling detect() fills in the necessary 
     * information for a lookup
     * @param eHeaders Enumeration of headers in key value pairs
     * @param consumer a class that implements HDAPIConsumer interface
     */
    public void detectInit(Enumeration eHeaders, HDAPIConsumer consumer){
        for (; eHeaders.hasMoreElements() ;) {
            String header = (String)eHeaders.nextElement();
            if(header.matches("/(^x|^X|^http|^HTTP|^Http)/")){
                try{
                    this.m_detectRequest.put(header, consumer.serveHeader(header));
                    System.out.println(header + " " + consumer.serveHeader(header));
                }catch(Exception ex){
                    System.out.println(ex.getMessage());
                }
            }
        }
        
        try{
            this.m_detectRequest.put("user-agent",consumer.serveHeader("user-agent"));
            this.m_detectRequest.put("ipaddress",consumer.serveIP());
            this.m_detectRequest.put("request_uri",consumer.serveRequestURI());
            
        }catch(Exception ex){
            System.out.println(ex.getMessage());
        }
        
    }
    
    /**
     * Queries handsetdetection.com for device capabilities
     * @param options all,display,geoip,product_info or handsetdetection.com 
     * for complete reference
     * @return true if successful and false otherwise
     */
    public boolean detect(String options){

        boolean ret = false;
        
        try{
            this.m_detectRequest.put("options", options);
            this.m_detectReply = sendjson(this.m_detectRequest, "/devices/detect.json");
            
            if(this.m_detectReply.has("message") && this.m_detectReply.getString("message").equalsIgnoreCase("OK")){
                ret = true;
            }
            
        }catch(Exception ex){
            System.out.println(ex.getMessage());
        }
        
        
        return ret;

    }
    
    /**
     * Fetches all supported Vendors available at handsetdetection.com
     * @return true on success and false otherwise
     */
    public boolean vendor(){
        
        boolean ret = false;
        
        try{
            this.m_vendorReply = sendjson(null, "/devices/vendors.json");
            
            if(this.m_vendorReply.has("message") && this.m_vendorReply.getString("message").equalsIgnoreCase("OK")){
                ret = true;
            }
            
        }catch(Exception ex){
            System.out.println(ex.getMessage());
        }
        
        
        return ret;
        
    }

    /**
     * Fetches all available phone models in handsetdetection.com database. 
     * If a vendor is specified then only models for that vendor are returned. 
     * Call getModel() to get access to the returned list.
     * @param vendor all or a valid vendor name
     * @return true if successful, false otherwise
     */
    public boolean model(String vendor){
        
        boolean ret = false;
        
        try{
            JSONObject vendorRequest = new JSONObject();
            vendorRequest.put("vendor", vendor);
            this.m_modelReply = sendjson(vendorRequest, "/devices/models.json");
            
            if(this.m_modelReply.has("message") && this.m_modelReply.getString("message").equalsIgnoreCase("OK")){
                ret = true;
            }
            
        }catch(Exception ex){
            System.out.println(ex.getMessage());
        }
        
        
        return ret;
        
    }
    
    /**
     * Utility method for querying device phone call string. detect() function 
     * must have been called prior to calling this function
     * @return empty string if not supported or on error. Valid call string on 
     * success
     */
    public String getDeviceClickToCall(){
        
        String ret = null;
        
        try{
            
            if(this.m_detectReply.has("xhtml_ui")){
                ret = this.m_detectReply.getJSONObject("xhtml_ui").getString("xhtml_make_phone_call_string");
            }else{
                ret = "";
            }
            
        }catch(Exception ex){
            ret = null;
            System.out.println(ex.getMessage());
        }
        
        return ret;
    }

    /**
     * Utility method for querying device screen resolution. detect() must have 
     * been called prior to calling this function
     * @return null or a valid Rectangle object
     */
    public java.awt.Rectangle getDeviceResolution(){
        
        Rectangle ret = null;
        
        try{
            
            if(this.m_detectReply.has("display")){

                ret = new Rectangle(
                    Integer.parseInt(this.m_detectReply.getJSONObject("display").getString("resolution_width")),
                    Integer.parseInt(this.m_detectReply.getJSONObject("display").getString("resolution_height"))
                );
                
            }else{
                ret = new Rectangle(0,0);
            }
            
        }catch(Exception ex){
            ret = null;
            System.out.println(ex.getMessage());
        }
        
        return ret;
    }
    
    /**
     * Utility method for returning the SMS sending string of the device 
     * if supported, detect() must have been called prior to calling this 
     * function.
     * @return must have been called prior to calling this function.
     */
    public String getDeviceSendSms(){
        
        String ret = null;
        
        try{
            
            if(this.m_detectReply.has("xhtml_ui")){
                ret = this.m_detectReply.getJSONObject("xhtml_ui").getString("xhtml_send_sms_string");
            }else{
                ret = "";
            }
            
        }catch(Exception ex){
            ret = null;
            System.out.println(ex.getMessage());
        }
        
        return ret;
    }
    
    /**
     * Generates the WSSE header for authentication
     * @param username
     * @param password
     * @return header authentication string
     * @throws java.lang.Exception
     */
    public String generateWSSEHeader(String username, String password)
        throws Exception
    {  
        byte[] nonceBytes = Long.toString(new Date().getTime()).getBytes();
        String nonce = new String(WSSEUtilities.base64Encode(nonceBytes));
        
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
        String created = sdf.format(new Date());
        
        String digest = WSSEUtilities.generateDigest(
                nonceBytes, created.getBytes("UTF-8"), password.getBytes("UTF-8"));
        
        StringBuffer header = new StringBuffer("UsernameToken Username=\"");
        header.append(username);
        header.append("\", ");
        header.append("PasswordDigest=\"");
        header.append(digest);
        header.append("\", ");
        header.append("Nonce=\"");
        header.append(nonce);
        header.append("\", ");
        header.append("Created=\"");
        header.append(created);
        header.append("\"");
        return header.toString();
    }
    
}
